﻿using Sidvall.Tasks;
using Sidvall.UI.Dialogs;
using System.Linq;
using System.Threading.Tasks;

namespace Sidvall.UI.Entities
{
    public sealed class MonitoredTaskItem : BusinessBase, System.IDisposable, System.IProgress<Sidvall.Tasks.MonitoredTaskProgress>
    {
        #region Public Members

        public event System.EventHandler<MonitoredTaskEventArgs> MonitoredTaskChanged;
        public event System.EventHandler<MonitoredTaskEventArgs> MonitoredTaskCompleted;

        #region Description

        private string _Description;
        public string Description
        {
            get
            {
                return _Description;
            }
            set
            {
                _Description = value;
                OnPropertyChanged(nameof(Description), true, true);
            }
        }

        #endregion
        #region Progress

        private double _Progress;
        public double Progress
        {
            get
            {
                return _Progress;
            }
            set
            {
                _Progress = value;
                OnPropertyChanged(nameof(Progress), true, true);
            }
        }

        #endregion
        #region StartTime

        private System.DateTime _StartTime;
        public System.DateTime StartTime
        {
            get
            {
                return Sidvall.Business.ValidationManager.GetValidDateTime(_StartTime);
            }
            set
            {
                _StartTime = Sidvall.Business.ValidationManager.GetValidUniversalDateTime(value, System.DateTimeKind.Utc);
                OnPropertyChanged(nameof(StartTime), true, true);
            }
        }

        #endregion
        #region CurrentTime

        private System.DateTime _CurrentTime;
        public System.DateTime CurrentTime
        {
            get
            {
                return Sidvall.Business.ValidationManager.GetValidDateTime(_CurrentTime);
            }
            set
            {
                _CurrentTime = Sidvall.Business.ValidationManager.GetValidUniversalDateTime(value, System.DateTimeKind.Utc);
                OnPropertyChanged(nameof(CurrentTime), true, true);
                OnPropertyChanged(nameof(CompletedTime), true, false);
                OnPropertyChanged(nameof(CompletedTimeText), true, false);
            }
        }

        #endregion
        #region CompletedTime

        public System.TimeSpan CompletedTime
        {
            get
            {
                return this.CurrentTime.Subtract(this.StartTime);
            }
        }

        #endregion
        #region CompletedTimeText

        public string CompletedTimeText
        {
            get
            {
                return this.CompletedTime.ToString(this.TimeFormat, System.Globalization.CultureInfo.CurrentCulture);
            }
        }

        #endregion
        #region TotalTime

        private System.TimeSpan? _TotalTime;
        public System.TimeSpan? TotalTime
        {
            get
            {
                return _TotalTime;
            }
            set
            {
                _TotalTime = value;
                OnPropertyChanged(nameof(TotalTime), true, true);
                OnPropertyChanged(nameof(TotalTimeText), true, false);
            }
        }

        #endregion
        #region TotalTimeText

        public string TotalTimeText
        {
            get
            {
                if (this.TotalTime == null)
                    return null;
                return this.TotalTime.Value.ToString(this.TimeFormat, System.Globalization.CultureInfo.CurrentCulture);
            }
        }

        #endregion
        #region TimeLeft

        private System.TimeSpan? _TimeLeft;
        public System.TimeSpan? TimeLeft
        {
            get
            {
                return _TimeLeft;
            }
            set
            {
                _TimeLeft = value;
                OnPropertyChanged(nameof(TimeLeft), true, true);
                OnPropertyChanged(nameof(TimeLeftText), true, false);
            }
        }

        #endregion
        #region TimeLeftText

        public string TimeLeftText
        {
            get
            {
                if (this.TimeLeft == null)
                    return null;
                return this.TimeLeft.Value.ToString(this.TimeFormat, System.Globalization.CultureInfo.CurrentCulture);
            }
        }

        #endregion
        #region TimeFormat

        private string _TimeFormat;
        public string TimeFormat
        {
            get
            {
                return _TimeFormat;
            }
            set
            {
                _TimeFormat = value;
                OnPropertyChanged(nameof(TimeFormat), true, true);
            }
        }

        #endregion
        #region Speed

        private double? _Speed;
        public double? Speed
        {
            get
            {
                return _Speed;
            }
            set
            {
                _Speed = value;
                OnPropertyChanged(nameof(Speed), true, true);
                OnPropertyChanged(nameof(SpeedText), true, false);
            }
        }

        #endregion
        #region SpeedFactor

        private double _SpeedFactor;
        public double SpeedFactor
        {
            get
            {
                return _SpeedFactor;
            }
            set
            {
                _SpeedFactor = value;
                OnPropertyChanged(nameof(SpeedFactor), true, true);
            }
        }

        #endregion
        #region SpeedFormat

        private string _SpeedFormat;
        public string SpeedFormat
        {
            get
            {
                return _SpeedFormat;
            }
            set
            {
                _SpeedFormat = value;
                OnPropertyChanged(nameof(SpeedFormat), true, true);
            }
        }

        #endregion
        #region SpeedText

        public string SpeedText
        {
            get
            {
                if (this.Speed == null)
                    return null;
                return this.Speed.Value.ToString(this.SpeedFormat, System.Globalization.CultureInfo.CurrentCulture);
            }
        }

        #endregion
        #region SpeedUnit

        private string _SpeedUnit;
        public string SpeedUnit
        {
            get
            {
                return _SpeedUnit;
            }
            set
            {
                _SpeedUnit = value;
                OnPropertyChanged(nameof(SpeedUnit), true, true);
            }
        }

        #endregion
        #region ShowProgress

        private bool _ShowProgress = true;
        public bool ShowProgress
        {
            get
            {
                return _ShowProgress;
            }
            set
            {
                _ShowProgress = value;
                OnPropertyChanged(nameof(ShowProgress), true, true);
            }
        }

        #endregion
        #region ShowSpeed

        private bool _ShowSpeed = true;
        public bool ShowSpeed
        {
            get
            {
                return _ShowSpeed;
            }
            set
            {
                _ShowSpeed = value;
                OnPropertyChanged(nameof(ShowSpeed), true, true);
            }
        }

        #endregion
        #region ShowTime

        private bool _ShowTime = true;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ShowTime")]
        public bool ShowTime
        {
            get
            {
                return _ShowTime;
            }
            set
            {
                _ShowTime = value;
                OnPropertyChanged(nameof(ShowTime), true, true);
            }
        }

        #endregion
        #region ShowTimeLeft

        private bool _ShowTimeLeft = true;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ShowTime")]
        public bool ShowTimeLeft
        {
            get
            {
                return _ShowTimeLeft;
            }
            set
            {
                _ShowTimeLeft = value;
                OnPropertyChanged(nameof(ShowTimeLeft), true, true);
            }
        }

        #endregion
        #region CanCancel

        private bool _CanCancel = true;
        public bool CanCancel
        {
            get
            {
                return _CanCancel;
            }
            set
            {
                _CanCancel = value;
                OnPropertyChanged(nameof(CanCancel), true, true);
            }
        }

        #endregion
        #region Exception

        private System.Exception _Exception;
        public System.Exception Exception
        {
            get
            {
                return _Exception;
            }
            set
            {
                _Exception = value;
                OnPropertyChanged(nameof(Exception), true, true);
            }
        }

        #endregion
        #region Status

        private Sidvall.Tasks.TaskStatus _Status = Sidvall.Tasks.TaskStatus.Created;
        public Sidvall.Tasks.TaskStatus Status
        {
            get
            {
                return _Status;
            }
            set
            {
                _Status = value;
                OnPropertyChanged(nameof(Status), true, true);
            }
        }

        #endregion
        #region Tasks

        private MonitoredTaskCollection _Tasks;
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
        public MonitoredTaskCollection Tasks
        {
            get
            {
                if (_Tasks == null)
                    _Tasks = new MonitoredTaskCollection();
                return _Tasks;
            }
        }

        #endregion

        #region Dispose

        public void Dispose()
        {
            if (this.TokenSource != null)
            {
                this.TokenSource.Dispose();
                this.TokenSource = null;
            }
            this.Tasks.Clear();
            System.GC.SuppressFinalize(this);
        }

        #endregion
        #region Cancel

        public bool Cancel()
        {
            if ((this.Status == Sidvall.Tasks.TaskStatus.Running) && (this.CanCancel))
            {
                this.Status = Sidvall.Tasks.TaskStatus.Canceled;
                this.Exception = null;
                this.TokenSource.Cancel();
                return true;
            }
            return false;
        }

        #endregion
        #region ExecuteAsync

        public async Task<bool> ExecuteAsync(bool executeTasksAsOne)
        {
            if (this.Tasks == null)
                return false;
            if (this.Status == Sidvall.Tasks.TaskStatus.Running)
                return false;
            this.Status = Sidvall.Tasks.TaskStatus.Running;
            if (this.TokenSource == null)
                this.TokenSource = new System.Threading.CancellationTokenSource();
            this.StartTime = System.DateTime.UtcNow;
            this.CurrentTime = this.StartTime;
            try
            {
                if (executeTasksAsOne)
                    await this.Tasks.ExecuteAsOneAsync(this, this.TokenSource.Token);
                else
                    await this.Tasks.ExecuteAsync(this, this.TokenSource.Token);
                this.Status = Sidvall.Tasks.TaskStatus.RanToCompletion;
            }
            catch (System.OperationCanceledException)
            {
                this.Status = Sidvall.Tasks.TaskStatus.Canceled;
            }
            catch (System.Exception ex)
            {
                this.Exception = ex;
                this.Status = Sidvall.Tasks.TaskStatus.Faulted;
            }
            switch (this.Status)
            {
                case Sidvall.Tasks.TaskStatus.Canceled:
                    this.Description = "Canceled";
                    break;
                case Sidvall.Tasks.TaskStatus.Faulted:
                    this.Description = Sidvall.SystemContext.Current.UserExceptionManager.GetMessage(this.Exception);
                    break;
                default:
                    this.Description = "Done";
                    break;
            }
            return true;
        }

        #endregion
        #region Initialize

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")]
        public void Initialize(ProgressDialogContext context)
        {
            if (context == null)
                return;
            _SpeedFactor = context.SpeedFactor;
            _SpeedFormat = context.SpeedFormat;
            _SpeedUnit = context.SpeedUnit;
            _TimeFormat = context.TimeFormat;
            _ShowProgress = context.ShowProgress;
            _ShowSpeed = context.ShowSpeed;
            _ShowTime = context.ShowTime;
            _ShowTimeLeft = context.ShowTimeLeft;
            _CanCancel = context.CanCancel;
            this.Tasks.AddRange(context.Tasks);
        }

        #endregion
        #region OnMonitoredTaskChanged

        private void OnMonitoredTaskChanged(string taskId)
        {
            var monitoredTask = (from o in this.Tasks
                                 where o.TaskId == taskId
                                 select o).FirstOrDefault();
            if (monitoredTask != null)
            {
                var args = new MonitoredTaskEventArgs(monitoredTask);
                OnMonitoredTaskChanged(args);
            }
        }
        private void OnMonitoredTaskChanged(MonitoredTaskEventArgs args)
        {
            if (this.MonitoredTaskChanged != null)
                this.MonitoredTaskChanged.Invoke(this, args);
        }

        #endregion
        #region OnMonitoredTaskCompleted

        private void OnMonitoredTaskCompleted(string taskId)
        {
            var monitoredTask = (from o in this.Tasks
                                 where o.TaskId == taskId
                                 select o).FirstOrDefault();
            if (monitoredTask != null)
            {
                var args = new MonitoredTaskEventArgs(monitoredTask);
                OnMonitoredTaskCompleted(args);
            }
        }
        private void OnMonitoredTaskCompleted(MonitoredTaskEventArgs args)
        {
            if (this.MonitoredTaskCompleted != null)
                this.MonitoredTaskCompleted.Invoke(this, args);
        }

        #endregion

        #endregion
        #region Private Members

        private System.Threading.CancellationTokenSource TokenSource { get; set; }

        #region IProgress Members

        void System.IProgress<MonitoredTaskProgress>.Report(MonitoredTaskProgress value)
        {
            if (value == null)
                throw new System.ArgumentNullException("value");
            this.CurrentTime = System.DateTime.UtcNow;
            if ((value.TotalWork > 0) && (value.CompletedWork > 0))
            {
                this.Progress = value.CompletedWork * 100.0 / value.TotalWork;
                this.TotalTime = new System.TimeSpan(this.CompletedTime.Ticks * value.TotalWork / value.CompletedWork);
                this.TimeLeft = this.TotalTime.Value.Subtract(this.CompletedTime);
                this.Speed = value.CompletedWork * this.SpeedFactor / this.CompletedTime.TotalSeconds;
                this.Description = value.Description;
                if (value.TotalWork != value.CompletedWork)
                    OnMonitoredTaskChanged(value.TaskId);
                else
                    OnMonitoredTaskCompleted(value.TaskId);
            }
            else
            {
                this.Progress = 0;
                this.TotalTime = null;
                this.TimeLeft = null;
                this.Speed = null;
                this.Description = value.Description;
            }
        }

        #endregion

        #endregion
        #region Constructors

        public MonitoredTaskItem()
        {
        }

        #endregion
    }
}
